home *** CD-ROM | disk | FTP | other *** search
/ IRIX 6.5 Applications 2004 April / SGI IRIX 6.5 Applications 2004 April.iso / dist / mozilla.idb / var / netscape / mozilla / components / nsProgressDialog.js.z / nsProgressDialog.js
Text File  |  2004-01-06  |  35KB  |  889 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Mozilla Progress Dialog.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Netscape Communications Corp.
  18.  * Portions created by the Initial Developer are Copyright (C) 2002
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Bill Law       <law@netscape.com>
  23.  *   Aaron Kaluszka <ask@swva.net>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. /* This file implements the nsIProgressDialog interface.  See nsIProgressDialog.idl
  40.  *
  41.  * The implementation consists of a JavaScript "class" named nsProgressDialog,
  42.  * comprised of:
  43.  *   - a JS constructor function
  44.  *   - a prototype providing all the interface methods and implementation stuff
  45.  *
  46.  * In addition, this file implements an nsIModule object that registers the
  47.  * nsProgressDialog component.
  48.  */
  49.  
  50. /* ctor
  51.  */
  52. function nsProgressDialog() {
  53.     // Initialize data properties.
  54.     this.mParent      = null;
  55.     this.mOperation   = null;
  56.     this.mStartTime   = ( new Date() ).getTime();
  57.     this.observer     = null;
  58.     this.mLastUpdate  = Number.MIN_VALUE; // To ensure first onProgress causes update.
  59.     this.mInterval    = 750; // Default to .75 seconds.
  60.     this.mElapsed     = 0;
  61.     this.mLoaded      = false;
  62.     this.fields       = new Array;
  63.     this.strings      = new Array;
  64.     this.mSource      = null;
  65.     this.mTarget      = null;
  66.     this.mMIMEInfo    = null;
  67.     this.mDialog      = null;
  68.     this.mDisplayName = null;
  69.     this.mPaused      = null;
  70.     this.mRequest     = null;
  71.     this.mCompleted   = false;
  72.     this.mMode        = "normal";
  73.     this.mPercent     = 0;
  74.     this.mRate        = 0;
  75.     this.mBundle      = null;
  76.     this.mCancelDownloadOnClose = true;
  77. }
  78.  
  79. const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  80.  
  81. nsProgressDialog.prototype = {
  82.     // Turn this on to get debugging messages.
  83.     debug: false,
  84.  
  85.     // Chrome-related constants.
  86.     dialogChrome:   "chrome://global/content/nsProgressDialog.xul",
  87.     dialogFeatures: "chrome,titlebar,minimizable=yes",
  88.  
  89.     // getters/setters
  90.     get saving()            { return this.MIMEInfo == null ||
  91.                               this.MIMEInfo.preferredAction == Components.interfaces.nsIMIMEInfo.saveToDisk; },
  92.     get parent()            { return this.mParent; },
  93.     set parent(newval)      { return this.mParent = newval; },
  94.     get operation()         { return this.mOperation; },
  95.     set operation(newval)   { return this.mOperation = newval; },
  96.     get observer()          { return this.mObserver; },
  97.     set observer(newval)    { return this.mObserver = newval; },
  98.     get startTime()         { return this.mStartTime; },
  99.     set startTime(newval)   { return this.mStartTime = newval/1000; }, // PR_Now() is in microseconds, so we convert.
  100.     get lastUpdate()        { return this.mLastUpdate; },
  101.     set lastUpdate(newval)  { return this.mLastUpdate = newval; },
  102.     get interval()          { return this.mInterval; },
  103.     set interval(newval)    { return this.mInterval = newval; },
  104.     get elapsed()           { return this.mElapsed; },
  105.     set elapsed(newval)     { return this.mElapsed = newval; },
  106.     get loaded()            { return this.mLoaded; },
  107.     set loaded(newval)      { return this.mLoaded = newval; },
  108.     get source()            { return this.mSource; },
  109.     set source(newval)      { return this.mSource = newval; },
  110.     get target()            { return this.mTarget; },
  111.     set target(newval)      { return this.mTarget = newval; },
  112.     get MIMEInfo()          { return this.mMIMEInfo; },
  113.     set MIMEInfo(newval)    { return this.mMIMEInfo = newval; },
  114.     get dialog()            { return this.mDialog; },
  115.     set dialog(newval)      { return this.mDialog = newval; },
  116.     get displayName()       { return this.mDisplayName; },
  117.     set displayName(newval) { return this.mDisplayName = newval; },
  118.     get paused()            { return this.mPaused; },
  119.     get request()           { return this.mRequest; },
  120.     get completed()         { return this.mCompleted; },
  121.     get mode()              { return this.mMode; },
  122.     get percent()           { return this.mPercent; },
  123.     get rate()              { return this.mRate; },
  124.     get kRate()             { return this.mRate / 1024; },
  125.     get cancelDownloadOnClose() { return this.mCancelDownloadOnClose; },
  126.     set cancelDownloadOnClose(newval) { return this.mCancelDownloadOnClose = newval; },
  127.  
  128.     // These setters use functions that update the dialog.
  129.     set paused(newval)      { return this.setPaused(newval); },
  130.     set request(newval)     { return this.setRequest(newval); },
  131.     set completed(newval)   { return this.setCompleted(newval); },
  132.     set mode(newval)        { return this.setMode(newval); },
  133.     set percent(newval)     { return this.setPercent(newval); },
  134.     set rate(newval)        { return this.setRate(newval); },
  135.  
  136.     // ---------- nsIProgressDialog methods ----------
  137.  
  138.     // open: Store aParentWindow and open the dialog.
  139.     open: function( aParentWindow ) {
  140.         // Save parent and "persist" operation.
  141.         this.parent    = aParentWindow;
  142.  
  143.         // Open dialog using the WindowWatcher service.
  144.         var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  145.                    .getService( Components.interfaces.nsIWindowWatcher );
  146.         this.dialog = ww.openWindow( this.parent,
  147.                                      this.dialogChrome,
  148.                                      null,
  149.                                      this.dialogFeatures,
  150.                                      this );
  151.     },
  152.     
  153.     init: function( aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, aOperation ) {
  154.       this.source = aSource;
  155.       this.target = aTarget;
  156.       this.displayName = aDisplayName;
  157.       this.MIMEInfo = aMIMEInfo;
  158.       if ( aStartTime ) {
  159.           this.startTime = aStartTime;
  160.       }
  161.       this.operation = aOperation;
  162.     },
  163.  
  164.     // ---------- nsIWebProgressListener methods ----------
  165.  
  166.     // Look for STATE_STOP and update dialog to indicate completion when it happens.
  167.     onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  168.         if ( aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP ) {
  169.             // we are done downloading...
  170.             this.completed = true;
  171.         }
  172.     },
  173.  
  174.     // Handle progress notifications.
  175.     onProgressChange: function( aWebProgress,
  176.                                 aRequest,
  177.                                 aCurSelfProgress,
  178.                                 aMaxSelfProgress,
  179.                                 aCurTotalProgress,
  180.                                 aMaxTotalProgress ) {
  181.         // Remember the request; this will also initialize the pause/resume stuff.
  182.         this.request = aRequest;
  183.  
  184.         var overallProgress = aCurTotalProgress;
  185.  
  186.         // Get current time.
  187.         var now = ( new Date() ).getTime();
  188.  
  189.         // If interval hasn't elapsed, ignore it.
  190.         if ( now - this.lastUpdate < this.interval &&
  191.              aMaxTotalProgress != "-1" && 
  192.              parseInt( aCurTotalProgress ) < parseInt( aMaxTotalProgress ) ) {
  193.             return;
  194.         }
  195.  
  196.         // Update this time.
  197.         this.lastUpdate = now;
  198.  
  199.         // Update elapsed time.
  200.         this.elapsed = now - this.startTime;
  201.  
  202.         // Calculate percentage.
  203.         if ( aMaxTotalProgress > 0) {
  204.             this.percent = Math.floor( ( overallProgress * 100.0 ) / aMaxTotalProgress );
  205.         } else {
  206.             this.percent = -1;
  207.         }
  208.  
  209.         // If dialog not loaded, then don't bother trying to update display.
  210.         if ( !this.loaded ) {
  211.             return;
  212.         }
  213.  
  214.         // Update dialog's display of elapsed time.
  215.         this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
  216.  
  217.         // Now that we've set the progress and the time, update # bytes downloaded...
  218.         // Update status (nnK of mmK bytes at xx.xK aCurTotalProgress/sec)
  219.         var status = this.getString( "progressMsg" );
  220.  
  221.         // Insert 1 is the number of kilobytes downloaded so far.
  222.         status = this.replaceInsert( status, 1, parseInt( overallProgress/1024 + .5 ) );
  223.  
  224.         // Insert 2 is the total number of kilobytes to be downloaded (if known).
  225.         if ( aMaxTotalProgress != "-1" ) {
  226.             status = this.replaceInsert( status, 2, parseInt( aMaxTotalProgress/1024 + .5 ) );
  227.         } else {
  228.             status = this.replaceInsert( status, 2, "??" );
  229.         }
  230.     
  231.         // Insert 3 is the download rate.
  232.         if ( this.elapsed ) {
  233.             this.rate = ( aCurTotalProgress * 1000 ) / this.elapsed;
  234.             status = this.replaceInsert( status, 3, this.rateToKRate( this.rate ) );
  235.         } else {
  236.             // Rate not established, yet.
  237.             status = this.replaceInsert( status, 3, "??.?" );
  238.         }
  239.     
  240.         // All 3 inserts are taken care of, now update status msg.
  241.         this.setValue( "status", status );
  242.     
  243.         // Update time remaining.
  244.         if ( this.rate && ( aMaxTotalProgress > 0 ) ) {
  245.             // Calculate how much time to download remaining at this rate.
  246.             var rem = Math.round( ( aMaxTotalProgress - aCurTotalProgress ) / this.rate );
  247.             this.setValue( "timeLeft", this.formatSeconds( rem ) );
  248.         } else {
  249.             // We don't know how much time remains.
  250.             this.setValue( "timeLeft", this.getString( "unknownTime" ) );
  251.         }
  252.     },
  253.  
  254.     // Look for error notifications and display alert to user.
  255.     onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  256.         // Check for error condition (only if dialog is still open).
  257.         if ( aStatus != Components.results.NS_OK ) {
  258.             if ( this.loaded ) {
  259.                 // Get prompt service.
  260.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  261.                                    .getService( Components.interfaces.nsIPromptService );
  262.                 // Display error alert (using text supplied by back-end).
  263.                 var title = this.getProperty( this.saving ? "savingAlertTitle" : "openingAlertTitle",
  264.                                               [ this.fileName() ], 
  265.                                               1 );
  266.                 prompter.alert( this.dialog, title, aMessage );
  267.     
  268.                 // Close the dialog.
  269.                 if ( !this.completed ) {
  270.                     this.onCancel();
  271.                 }
  272.             } else {
  273.                 // Error occurred prior to onload even firing.
  274.                 // We can't handle this error until we're done loading, so
  275.                 // defer the handling of this call.
  276.                 this.dialog.setTimeout( function(obj,wp,req,stat,msg){obj.onStatusChange(wp,req,stat,msg)},
  277.                                         100, this, aWebProgress, aRequest, aStatus, aMessage );
  278.             }
  279.         }
  280.     },
  281.  
  282.     // Ignore onLocationChange and onSecurityChange notifications.
  283.     onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  284.     },
  285.  
  286.     onSecurityChange: function( aWebProgress, aRequest, state ) {
  287.     },
  288.  
  289.     // ---------- nsIObserver methods ----------
  290.     observe: function( anObject, aTopic, aData ) {
  291.         // Something of interest occured on the dialog.
  292.         // Dispatch to corresponding implementation method.
  293.         switch ( aTopic ) {
  294.         case "onload":
  295.             this.onLoad();
  296.             break;
  297.         case "oncancel":
  298.             this.onCancel();
  299.             break;
  300.         case "onpause":
  301.             this.onPause();
  302.             break;
  303.         case "onlaunch":
  304.             this.onLaunch();
  305.             break;
  306.         case "onreveal":
  307.             this.onReveal();
  308.             break;
  309.         case "onunload":
  310.             this.onUnload();
  311.             break;
  312.         case "oncompleted":
  313.             // This event comes in when setCompleted needs to be deferred because
  314.             // the dialog isn't loaded yet.
  315.             this.completed = true;
  316.             break;
  317.         default:
  318.             break;
  319.         }
  320.     },
  321.  
  322.     // ---------- nsISupports methods ----------
  323.  
  324.     // This "class" supports nsIProgressDialog, nsIWebProgressListener (by virtue
  325.     // of interface inheritance), nsIObserver, and nsISupports.
  326.     QueryInterface: function (iid) {
  327.         if (!iid.equals(Components.interfaces.nsIProgressDialog) &&
  328.             !iid.equals(Components.interfaces.nsIDownload) && 
  329.             !iid.equals(Components.interfaces.nsIWebProgressListener) &&
  330.             !iid.equals(Components.interfaces.nsIObserver) &&
  331.             !iid.equals(Components.interfaces.nsISupports)) {
  332.             throw Components.results.NS_ERROR_NO_INTERFACE;
  333.         }
  334.         return this;
  335.     },
  336.  
  337.     // ---------- implementation methods ----------
  338.  
  339.     // Initialize the dialog.
  340.     onLoad: function() {
  341.         // Note that onLoad has finished.
  342.         this.loaded = true;
  343.  
  344.         // Fill dialog.
  345.         this.loadDialog();
  346.  
  347.         // Position dialog.
  348.         if ( this.dialog.opener ) {
  349.             this.dialog.moveToAlertPosition();
  350.         } else {
  351.             this.dialog.centerWindowOnScreen();
  352.         }
  353.  
  354.         // Set initial focus on "keep open" box.  If that box is hidden, or, if
  355.         // the download is already complete, then focus is on the cancel/close
  356.         // button.  The download may be complete if it was really short and the
  357.         // dialog took longer to open than to download the data.
  358.         if ( !this.completed && !this.saving ) {
  359.             this.dialogElement( "keep" ).focus();
  360.         } else {
  361.             this.dialogElement( "cancel" ).focus();
  362.         }
  363.     },
  364.  
  365.     // load dialog with initial contents
  366.     loadDialog: function() {
  367.         // Check whether we're saving versus opening with a helper app.
  368.         if ( !this.saving ) {
  369.             // Put proper label on source field.
  370.             this.setValue( "sourceLabel", this.getString( "openingSource" ) );
  371.  
  372.             // Target is the "preferred" application.  Hide if empty.
  373.             if ( this.MIMEInfo && this.MIMEInfo.preferredApplicationHandler ) {
  374.                 var appName = this.MIMEInfo.preferredApplicationHandler.leafName;
  375.                 if ( appName == null || appName.length == 0 ) {
  376.                     this.hide( "targetRow" );
  377.                 } else {
  378.                     // Use the "with:" label.
  379.                     this.setValue( "targetLabel", this.getString( "openingTarget" ) );
  380.                     // Name of application.
  381.                     this.setValue( "target", appName );
  382.                 }
  383.            } else {
  384.                this.hide( "targetRow" );
  385.            }
  386.         } else {
  387.             // Target is the destination file.
  388.             this.setValue( "target", this.target.path );
  389.         }
  390.  
  391.         // Set source field.
  392.         this.setValue( "source", this.source.spec );
  393.  
  394.         var now = ( new Date() ).getTime();
  395.  
  396.         // Intialize the elapsed time.
  397.         if ( !this.elapsed ) {
  398.             this.elapsed = now - this.startTime;
  399.         }
  400.  
  401.         // Update elapsed time display.
  402.         this.setValue( "timeElapsed", this.formatSeconds( this.elapsed / 1000 ) );
  403.         this.setValue( "timeLeft", this.getString( "unknownTime" ) );
  404.  
  405.         // Initialize the "keep open" box.  Hide this if we're opening a helper app.
  406.         if ( !this.saving ) {
  407.             // Hide this in this case.
  408.             this.hide( "keep" );
  409.         } else {
  410.             // Initialize using last-set value from prefs.
  411.             var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
  412.                             .getService( Components.interfaces.nsIPrefBranch );
  413.             if ( prefs ) {
  414.                 this.dialogElement( "keep" ).checked = prefs.getBoolPref( "browser.download.progressDnldDialog.keepAlive" );
  415.             }
  416.         }
  417.  
  418.         // Initialize title.
  419.         this.setTitle();
  420.     },
  421.  
  422.     // Cancel button stops the download (if not completed),
  423.     // and closes the dialog.
  424.     onCancel: function() {
  425.          // Cancel the download, if not completed.
  426.          if ( !this.completed ) {
  427.              if ( this.operation ) {
  428.                  this.operation.cancelSave();
  429.                  // XXX We're supposed to clean up files/directories.
  430.              }
  431.              if ( this.observer ) {
  432.                  this.observer.observe( this, "oncancel", "" );
  433.              }
  434.              this.paused = false;
  435.          }
  436.         // Test whether the dialog is already closed.
  437.         // This will be the case if we've come through onUnload.
  438.         if ( this.dialog ) {
  439.             // Close the dialog.
  440.             this.dialog.close();
  441.         }
  442.     },
  443.  
  444.     // onunload event means the dialog has closed.
  445.     // We go through our onCancel logic to stop the download if still in progress.
  446.     onUnload: function() {
  447.         // Remember "keep dialog open" setting, if visible.
  448.         if ( this.saving ) {
  449.             var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  450.                           .getService( Components.interfaces.nsIPrefBranch );
  451.             if ( prefs ) {
  452.                 prefs.setBoolPref( "browser.download.progressDnldDialog.keepAlive", this.dialogElement( "keep" ).checked );
  453.             }
  454.          }
  455.          this.dialog = null; // The dialog is history.
  456.          if ( this.mCancelDownloadOnClose ) {
  457.              this.onCancel();
  458.          }
  459.     },
  460.  
  461.     // onpause event means the user pressed the pause/resume button
  462.     // Toggle the pause/resume state (see the function setPause(), below).i
  463.     onPause: function() {
  464.          this.paused = !this.paused;
  465.     },
  466.  
  467.     // onlaunch event means the user pressed the launch button
  468.     // Invoke the launch method of the target file.
  469.     onLaunch: function() {
  470.          try {
  471.            const kDontAskAgainPref  = "browser.download.progressDnlgDialog.dontAskForLaunch";
  472.            try {
  473.              var pref = Components.classes["@mozilla.org/preferences-service;1"]
  474.                               .getService(Components.interfaces.nsIPrefBranch);
  475.              var dontAskAgain = pref.getBoolPref(kDontAskAgainPref);
  476.            } catch (e) {
  477.              // we need to ask if we're unsure
  478.              dontAskAgain = false;
  479.            }
  480.            if ( !dontAskAgain && this.target.isExecutable() ) {
  481.              try {
  482.                var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  483.                                                .getService( Components.interfaces.nsIPromptService );
  484.              } catch (ex) {
  485.                // getService doesn't return null, it throws
  486.                return;
  487.              }
  488.              var title = this.getProperty( "openingAlertTitle",
  489.                                            [ this.fileName() ],
  490.                                            1 );
  491.              var msg = this.getProperty( "securityAlertMsg",
  492.                                          [ this.fileName() ],
  493.                                          1 );
  494.              var dontaskmsg = this.getProperty( "dontAskAgain",
  495.                                                 [ ], 0 );
  496.              var checkbox = {value:0};
  497.              var okToProceed = promptService.confirmCheck(this.dialog, title, msg, dontaskmsg, checkbox);
  498.              try {
  499.                if (checkbox.value != dontAskAgain)
  500.                  pref.setBoolPref(kDontAskAgainPref, checkbox.value);
  501.              } catch (ex) {}
  502.  
  503.              if ( !okToProceed )
  504.                return;
  505.            }
  506.            this.target.launch();
  507.            this.dialog.close();
  508.          } catch ( exception ) {
  509.              // XXX Need code here to tell user the launch failed!
  510.              dump( "nsProgressDialog::onLaunch failed: " + exception + "\n" );
  511.          }
  512.     },
  513.  
  514.     // onreveal event means the user pressed the "reveal location" button
  515.     // Invoke the reveal method of the target file.
  516.     onReveal: function() {
  517.          try {
  518.              this.target.reveal();
  519.              this.dialog.close();
  520.          } catch ( exception ) {
  521.          }
  522.     },
  523.  
  524.     // Get filename from target file.
  525.     fileName: function() {
  526.         return this.target ? this.target.leafName : "";
  527.     },
  528.  
  529.     // Set the dialog title.
  530.     setTitle: function() {
  531.         // Start with saving/opening template.
  532.         // If percentage is not known (-1), use alternate template
  533.         var title = this.saving 
  534.             ? ( this.percent != -1 ? this.getString( "savingTitle" ) : this.getString( "unknownSavingTitle" ) )
  535.             : ( this.percent != -1 ? this.getString( "openingTitle" ) : this.getString( "unknownOpeningTitle" ) );
  536.  
  537.  
  538.         // Use file name as insert 1.
  539.         title = this.replaceInsert( title, 1, this.fileName() );
  540.  
  541.         // Use percentage as insert 2 (if known).
  542.         if ( this.percent != -1 ) {
  543.             title = this.replaceInsert( title, 2, this.percent );
  544.         }
  545.  
  546.         // Set <window>'s title attribute.
  547.         if ( this.dialog ) {
  548.             this.dialog.title = title;
  549.         }
  550.     },
  551.  
  552.     // Update the dialog to indicate specified percent complete.
  553.     setPercent: function( percent ) {
  554.         // Maximum percentage is 100.
  555.         if ( percent > 100 ) {
  556.             percent = 100;
  557.         }
  558.         // Test if percentage is changing.
  559.         if ( this.percent != percent ) {
  560.             this.mPercent = percent;
  561.  
  562.             // If dialog not opened yet, bail early.
  563.             if ( !this.loaded ) {
  564.                 return this.mPercent;
  565.             }
  566.  
  567.             if ( percent == -1 ) {
  568.                 // Progress meter needs to be in "undetermined" mode.
  569.                 this.mode = "undetermined";
  570.  
  571.                 // Update progress meter percentage text.
  572.                 this.setValue( "progressText", "" );
  573.             } else {
  574.                 // Progress meter needs to be in normal mode.
  575.                 this.mode = "normal";
  576.  
  577.                 // Set progress meter thermometer.
  578.                 this.setValue( "progress", percent );
  579.  
  580.                 // Update progress meter percentage text.
  581.                 this.setValue( "progressText", this.replaceInsert( this.getString( "percentMsg" ), 1, percent ) );
  582.             }
  583.     
  584.             // Update title.
  585.             this.setTitle();
  586.         }
  587.         return this.mPercent;
  588.     },
  589.  
  590.     // Update download rate and dialog display.
  591.     // Note that we don't want the displayed value to quiver
  592.     // between essentially identical values (e.g., 99.9Kb and
  593.     // 100.0Kb) so we only update if we see a big change.
  594.     setRate: function( rate ) {
  595.         if ( rate ) {
  596.             // rate is bytes/sec
  597.             var change = Math.abs( this.rate - rate );
  598.             // Don't update too often!
  599.             if ( change > this.rate / 10 ) {
  600.                 // Displayed rate changes.
  601.                 this.mRate = rate;
  602.             }
  603.         }
  604.         return this.mRate;
  605.     },
  606.  
  607.     // Handle download completion.
  608.     setCompleted: function() {
  609.         // If dialog hasn't loaded yet, defer this.
  610.         if ( !this.loaded ) {
  611.             this.dialog.setTimeout( function(obj){obj.setCompleted()}, 100, this );
  612.             return false;
  613.         }
  614.         if ( !this.mCompleted ) {
  615.             this.mCompleted = true;
  616.  
  617.             // If the "keep dialog open" box is checked, then update dialog.
  618.             if ( this.dialog && this.dialogElement( "keep" ).checked ) {
  619.                 // Indicate completion in status area.
  620.                 this.setValue( "status", this.replaceInsert( this.getString( "completeMsg" ),
  621.                                                              1,
  622.                                                              this.formatSeconds( this.elapsed/1000 ) ) );
  623.  
  624.                 // Put progress meter at 100%.
  625.                 this.percent = 100;
  626.  
  627.                 // Set time remaining to 00:00.
  628.                 this.setValue( "timeLeft", this.formatSeconds( 0 ) );
  629.  
  630.                 // Change Cancel button to Close, and give it focus.
  631.                 var cancelButton = this.dialogElement( "cancel" );
  632.                 cancelButton.label = this.getString( "close" );
  633.                 cancelButton.focus();
  634.  
  635.                 // Activate reveal/launch buttons if we enable them.
  636.                 var enableButtons = true;
  637.                 try {
  638.                   var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
  639.                                   .getService( Components.interfaces.nsIPrefBranch );
  640.                   enableButtons = prefs.getBoolPref( "browser.download.progressDnldDialog.enable_launch_reveal_buttons" );
  641.                 } catch ( e ) {
  642.                 }
  643.  
  644.                 if ( enableButtons ) {
  645.                     this.enable( "reveal" );
  646.                     try {
  647.                         if ( this.target ) {
  648.                             this.enable( "launch" );
  649.                         }
  650.                     } catch(e) {
  651.                     }
  652.                 }
  653.  
  654.                 // Disable the Pause/Resume buttons.
  655.                 this.dialogElement( "pauseResume" ).disabled = true;
  656.  
  657.                 // Fix up dialog layout (which gets messed up sometimes).
  658.                 this.dialog.sizeToContent();
  659.             } else if ( this.dialog ) {
  660.                 this.dialog.close();
  661.             }
  662.         }
  663.         return this.mCompleted;
  664.     },
  665.  
  666.     // Set progress meter to given mode ("normal" or "undetermined").
  667.     setMode: function( newMode ) {
  668.         if ( this.mode != newMode ) {
  669.             // Need to update progress meter.
  670.             this.dialogElement( "progress" ).setAttribute( "mode", newMode );
  671.         }
  672.         return this.mMode = newMode;
  673.     },
  674.  
  675.     // Set pause/resume state.
  676.     setPaused: function( pausing ) {
  677.         // If state changing, then update stuff.
  678.         if ( this.paused != pausing ) {
  679.             var string = pausing ? "resume" : "pause";
  680.             this.dialogElement( "pauseResume" ).label = this.getString(string);
  681.  
  682.             // If we have a request, suspend/resume it.
  683.             if ( this.request ) {
  684.                 if ( pausing ) {
  685.                     this.request.suspend();
  686.                 } else {
  687.                     this.request.resume();
  688.                 }
  689.             }
  690.         }
  691.         return this.mPaused = pausing;
  692.     },
  693.  
  694.     // Set the saved nsIRequest.  The first time, we test it for
  695.     // ftp and initialize the pause/resume stuff.
  696.     // XXX This is broken, I think, because if we're doing something
  697.     //     like saving a web-page-complete that has multiple images
  698.     //     accessed via ftp: urls, then it seems like the pause/resume
  699.     //     button should come and go, depending on what's being downloaded
  700.     //     at a given point in time.  The old dialog didn't handle that case
  701.     //     either, though, so I'm not sweating it for now.
  702.     setRequest: function( aRequest ) {
  703.         if ( this.request == null && this.loaded && aRequest ) {
  704.             // Right now, all that supports restarting downloads is ftp (rfc959).
  705.             try {
  706.                 ftpChannel = aRequest.QueryInterface( Components.interfaces.nsIFTPChannel );
  707.                 if ( ftpChannel ) {
  708.                     this.dialogElement("pauseResume").label = this.getString("pause");
  709.                     this.paused = false;
  710.                 }
  711.             } catch ( e ) {
  712.             }
  713.             // This *must* come after the "this.paused = false" above, so that we
  714.             // don't suspend or resume the first time we call that function!
  715.             this.mRequest = aRequest;
  716.         }
  717.         return this.mRequest;
  718.     },
  719.  
  720.     // Convert raw rate (bytes/sec) to Kbytes/sec (to nearest tenth).
  721.     rateToKRate: function( rate ) {
  722.         return ( rate / 1024 ).toFixed(1);
  723.     },
  724.  
  725.     // Format number of seconds in hh:mm:ss form.
  726.     formatSeconds: function( secs ) {
  727.         // Round the number of seconds to remove fractions.
  728.         secs = parseInt( secs + .5 );
  729.         var hours = parseInt( secs/3600 );
  730.         secs -= hours*3600;
  731.         var mins = parseInt( secs/60 );
  732.         secs -= mins*60;
  733.         var result;
  734.         if ( hours )
  735.             result = this.getString( "longTimeFormat" );
  736.         else
  737.             result = this.getString( "shortTimeFormat" );
  738.     
  739.         if ( hours < 10 )
  740.             hours = "0" + hours;
  741.         if ( mins < 10 )
  742.             mins = "0" + mins;
  743.         if ( secs < 10 )
  744.             secs = "0" + secs;
  745.     
  746.         // Insert hours, minutes, and seconds into result string.
  747.         result = this.replaceInsert( result, 1, hours );
  748.         result = this.replaceInsert( result, 2, mins );
  749.         result = this.replaceInsert( result, 3, secs );
  750.     
  751.         return result;
  752.     },
  753.  
  754.     // Get dialog element using argument as id.
  755.     dialogElement: function( id ) {
  756.         // Check if we've already fetched it.
  757.         if ( !( id in this.fields ) ) {
  758.             // No, then get it from dialog.
  759.             try {
  760.                 this.fields[ id ] = this.dialog.document.getElementById( id );
  761.             } catch(e) {
  762.                 this.fields[ id ] = { 
  763.                     value: "",
  764.                     setAttribute: function(id,val) {},
  765.                     removeAttribute: function(id) {}
  766.                     }
  767.             }
  768.         }
  769.         return this.fields[ id ];
  770.     },
  771.  
  772.     // Set dialog element value for given dialog element.
  773.     setValue: function( id, val ) {
  774.         this.dialogElement( id ).value = val;
  775.     },
  776.  
  777.     // Enable dialgo element.
  778.     enable: function( field ) {
  779.         this.dialogElement( field ).removeAttribute( "disabled" );
  780.     },
  781.  
  782.     // Get localizable string from properties file.
  783.     getProperty: function( propertyId, strings, len ) {
  784.         if ( !this.mBundle ) {
  785.             this.mBundle = Components.classes[ "@mozilla.org/intl/stringbundle;1" ]
  786.                              .getService( Components.interfaces.nsIStringBundleService )
  787.                                .createBundle( "chrome://global/locale/nsProgressDialog.properties");
  788.         }
  789.         return this.mBundle.formatStringFromName( propertyId, strings, len );
  790.     },
  791.  
  792.     // Get localizable string (from dialog <data> elements).
  793.     getString: function ( stringId ) {
  794.         // Check if we've fetched this string already.
  795.         if ( !( this.strings && stringId in this.strings ) ) {
  796.             // Presume the string is empty if we can't get it.
  797.             this.strings[ stringId ] = "";
  798.             // Try to get it.
  799.             try {
  800.                 this.strings[ stringId ] = this.dialog.document.getElementById( "string."+stringId ).childNodes[0].nodeValue;
  801.             } catch (e) {}
  802.        }
  803.        return this.strings[ stringId ];
  804.     },
  805.     
  806.     // Replaces insert ("#n") with input text.
  807.     replaceInsert: function( text, index, value ) {
  808.         var result = text;
  809.         var regExp = new RegExp( "#"+index );
  810.         result = result.replace( regExp, value );
  811.         return result;
  812.     },
  813.  
  814.     // Hide a given dialog field.
  815.     hide: function( field ) {
  816.         this.dialogElement( field ).setAttribute( "style", "display: none;" );
  817.         // Hide the associated separator, too.
  818.         this.dialogElement( field+"Separator" ).setAttribute( "style", "display: none;" );
  819.     },
  820.  
  821.     // Return input in hex, prepended with "0x" and leading zeros (to 8 digits).
  822.     hex: function( x ) {
  823.          var hex = Number(x).toString(16);
  824.          return "0x" + ( "00000000" + hex ).substring( hex.length );
  825.     },
  826.  
  827.     // Dump text (if debug is on).
  828.     dump: function( text ) {
  829.         if ( this.debug ) {
  830.             dump( text );
  831.         }
  832.     }
  833. }
  834.  
  835. // This Component's module implementation.  All the code below is used to get this
  836. // component registered and accessible via XPCOM.
  837. var module = {
  838.     // registerSelf: Register this component.
  839.     registerSelf: function (compMgr, fileSpec, location, type) {
  840.         compMgr = compMgr.QueryInterface( Components.interfaces.nsIComponentManagerObsolete );
  841.         compMgr.registerComponentWithType( this.cid,
  842.                                            "Mozilla Download Progress Dialog",
  843.                                            this.contractId,
  844.                                            fileSpec,
  845.                                            location,
  846.                                            true,
  847.                                            true,
  848.                                            type );
  849.     },
  850.  
  851.     // getClassObject: Return this component's factory object.
  852.     getClassObject: function (compMgr, cid, iid) {
  853.         if (!cid.equals(this.cid))
  854.             throw Components.results.NS_ERROR_NO_INTERFACE;
  855.  
  856.         if (!iid.equals(Components.interfaces.nsIFactory))
  857.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  858.  
  859.         return this.factory;
  860.     },
  861.  
  862.     /* CID for this class */
  863.     cid: Components.ID("{F5D248FD-024C-4f30-B208-F3003B85BC92}"),
  864.  
  865.     /* Contract ID for this class */
  866.     contractId: "@mozilla.org/progressdialog;1",
  867.  
  868.     /* factory object */
  869.     factory: {
  870.         // createInstance: Return a new nsProgressDialog object.
  871.         createInstance: function (outer, iid) {
  872.             if (outer != null)
  873.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  874.  
  875.             return (new nsProgressDialog()).QueryInterface(iid);
  876.         }
  877.     },
  878.  
  879.     // canUnload: n/a (returns true)
  880.     canUnload: function(compMgr) {
  881.         return true;
  882.     }
  883. };
  884.  
  885. // NSGetModule: Return the nsIModule object.
  886. function NSGetModule(compMgr, fileSpec) {
  887.     return module;
  888. }
  889.